package it.uniroma2.art.coda.projectionrule;

import it.uniroma2.art.coda.projectionrule.graph.GraphSingleElemPlaceholder;
import it.uniroma2.art.coda.projectionrule.graph.GraphSingleElemString;
import it.uniroma2.art.coda.projectionrule.graph.GraphSingleElemUri;
import it.uniroma2.art.coda.projectionrule.graph.GraphSingleElemVar;
import it.uniroma2.art.coda.projectionrule.graph.GraphSingleElement;
import it.uniroma2.art.coda.structures.depends.DependencyObjThreeParam;
import it.uniroma2.art.coda.structures.depends.DependencyObjVarParam;
import it.uniroma2.art.coda.structures.depends.DependencyObject;
import it.uniroma2.art.coda.structures.depends.DependencyObjTwoParam;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Andrea Turbati
 */

public class ProjectionRulesModel {
	private Map<String, Collection<ProjectionRule>> prMap; // the key is the uima type
	private Map<String, ProjectionRule> prMapWithId;
	
	private static Logger logger = LoggerFactory.getLogger(ProjectionRulesModel.class);

	public ProjectionRulesModel() {
		initialize();
	}

	public void initialize() {
		prMap = new HashMap<String, Collection<ProjectionRule>>();
		prMapWithId = new HashMap<String, ProjectionRule>();
	}

	public void addProjectionRule(ProjectionRule pr) {
		if (prMap.containsKey(pr.getUIMAType()) == false)
			prMap.put(pr.getUIMAType(), new ArrayList<ProjectionRule>());
		Collection<ProjectionRule> projectionRuleList = prMap.get(pr.getUIMAType());
		projectionRuleList.add(pr);
		if (pr.isIdNull() == false)
			prMapWithId.put(pr.getId(), pr);
	}

	public Collection<ProjectionRule> getListProjectionRules(String typeSystem) {
		return prMap.get(typeSystem);
	}

	public Map<String, Collection<ProjectionRule>> getMapProjectionRules() {
		return prMap;
	}

	public ProjectionRule getProjRuleFromId(String id) {
		return prMapWithId.get(id);
	}

	public void revolveDependecyAmongProjRules() {
		Collection<Collection<ProjectionRule>> prListComplete = prMap.values();
		for (Collection<ProjectionRule> projRuleListByType : prListComplete) {
			for (ProjectionRule projRule : projRuleListByType) {
				// set the dependency between this projection rule and the other at the top level
				Map<String, Collection<DependencyObject>> tempDepMap = projRule.getTempDependencyMap();
				Iterator<String> depTypeIter = tempDepMap.keySet().iterator();
				while (depTypeIter.hasNext()) {
					String depType = depTypeIter.next();
					Collection<DependencyObject> depIdForATypeList = tempDepMap.get(depType);
					Iterator<DependencyObject> depPrIdIter = depIdForATypeList.iterator();
					while (depPrIdIter.hasNext()) {
						DependencyObject depPrObj = depPrIdIter.next();
						if(depPrObj.isDynamicDependency()){
							projRule.addDependency(depType, depPrObj);
							//this is a dynamic dependency, so it is not possible to assign a static target projection
							//rule using the rule id
							continue;
						}
						ProjectionRule projRuleDependency = prMapWithId
								.get(depPrObj.getRuleId());
						if (projRuleDependency != null) {
							depPrObj.setProjRule(projRuleDependency);
							projRule.addDependency(depType, depPrObj);
							projRuleDependency.addReferredBy(depType, projRule);
						}
					}
				}

				// set the dependency for each element, where needed, in the GRAPH triples
				//be careful that there may exist dynamic reference
				Collection<GraphStruct> graphList = projRule.getGraphList();
				for (GraphStruct graph : graphList) {
					resolveGraphSingleDependency(graph.getSubj());
					resolveGraphSingleDependency(graph.getPred());
					resolveGraphSingleDependency(graph.getObj());
				}

				// set the dependency for each element, where needed, in the WHERE triples
				//be careful that there may exist dynamic reference
				Collection<GraphStruct> whereList = projRule.getWhereList();
				for (GraphStruct graph : whereList) {
					resolveGraphSingleDependency(graph.getSubj());
					resolveGraphSingleDependency(graph.getPred());
					resolveGraphSingleDependency(graph.getObj());
				}
			}

		}
	}

	private void resolveGraphSingleDependency(GraphSingleElement graphSingle) {
		if (graphSingle.hasIssue()) // the element has already an issue
			return;
		if (graphSingle instanceof GraphSingleElemPlaceholder) {
			GraphSingleElemPlaceholder graphSinglePlaceholder = (GraphSingleElemPlaceholder) graphSingle;
			if (graphSinglePlaceholder.hasExternalReference()) {
				String depPrId = graphSinglePlaceholder.getOtherProjRuleId();
				ProjectionRule otherProjRule = prMapWithId.get(depPrId);
				if (otherProjRule != null) {
					String placeholderId = graphSinglePlaceholder.getPlaceholderId();
					PlaceholderStruct placeholder = otherProjRule.getPlaceholderMap().get(placeholderId);
					if (placeholder != null) // it depends on a non existing node
						graphSinglePlaceholder.setPlaceholder(placeholder);
				} else if(!graphSinglePlaceholder.hasDynRef()) { // it depends on a projection rule that does not exist and it is not a dynamic reference
					graphSinglePlaceholder.setIssue(true);
				}
			}
		}
	}

	/*
	 * this method is used only for debugging
	 */
	public void printModel() {
		logger.debug("/**********************************/");
		logger.debug("/**Printing Projection Rule Model**/");
		logger.debug("/**********************************/");
		//System.out.println("/**********************************/"); // print
		//System.out.println("/**Printing Projection Rule Model**/"); // print
		//System.out.println("/**********************************/"); // print

		Set<String> keys = prMap.keySet();
		Iterator<String> iterKeys = keys.iterator();
		while (iterKeys.hasNext()) {
			logger.debug("");
			String uimaType = iterKeys.next();
			Collection<ProjectionRule> prCollection = prMap.get(uimaType);
			Iterator<ProjectionRule> iterPR = prCollection.iterator();
			while (iterPR.hasNext()) {
				ProjectionRule pr = iterPR.next();

				logger.debug("key = " + uimaType); // print
				double conf = pr.getConfidenceValue();
				logger.debug("\tprob = " + conf); // print

				// print the id of the Projection Rule
				logger.debug("\tid = " + pr.getId()); // print

				// print the list of type and prId of the Projection Rule this Projection Rule depends on
				Map<String, Collection<DependencyObject>> depMap = pr.getDependencyMap();
				Iterator<String> depTypeIter = depMap.keySet().iterator();
				logger.debug("\tDependency: "); // print
				while (depTypeIter.hasNext()) {
					String depType = depTypeIter.next();
					Collection<DependencyObject> depObjList = depMap.get(depType);
					logger.debug("\t\ttype: " + depType); // print
					for (DependencyObject depObj : depObjList) {
						logger.debug("\t\t\tprId: " + depObj.getRuleId()); // print
						logger.debug("\t\t\tisDependsOn: " + depObj.isExtDepADependsOn()); // print
						if (depObj instanceof DependencyObjTwoParam) {
							logger.debug("\t\t\t\tvalue: " + ((DependencyObjTwoParam) depObj).getSecondValue()); // print
						}
						else if (depObj instanceof DependencyObjThreeParam) {
							logger.debug("\t\t\t\tvalue2: " + ((DependencyObjThreeParam) depObj).getSecondValue()); // print
							logger.debug("\t\t\t\tvalue3: " + ((DependencyObjThreeParam) depObj).getThirdValue()); // print
						}
						else if(depObj instanceof DependencyObjVarParam){
							DependencyObjVarParam depObjVarParam = (DependencyObjVarParam) depObj;
							String [] valuesArray = depObjVarParam.getValuesArray();
							for(String value : valuesArray)
								logger.debug("\t\t\t\tvalue: " + value); // print
						}
					}
				}

				// print all the nodes of the selected Projection Rule
				Collection<PlaceholderStruct> placeholderList = pr.getPlaceholderMap().values();
				logger.debug("\tNODES:"); // print
				for (PlaceholderStruct placeholder : placeholderList) {
					String placeholderId = placeholder.getName();
					boolean isAlias = placeholder.isAlias();
					String type = placeholder.getType();
					boolean hasIfElse = placeholder.hasIfElse();
					logger.debug("\t\tNODE:"); // print
					logger.debug("\t\t\tnodeId = " + placeholderId); // print
					logger.debug("\t\t\tisAlias = " + isAlias); // print
					logger.debug("\t\t\ttype = " + type); // print
					if (hasIfElse) {
						List<IfElseStruct> ifElseList = placeholder.getIfElseList();
						for (IfElseStruct ifElseStruct : ifElseList) {
							String condType = ifElseStruct.getCondType();
							logger.debug("\t\t\t\tConditional assignement: " + condType); // print
							if(condType.compareTo("else") != 0){
								String condAlias = ifElseStruct.getCondAlias().getName();
								String condOper = ifElseStruct.getCondOper();
								String condValue = ifElseStruct.getCondValue();
								logger.debug("\t\t\t\t\t" + "(" + condAlias + " " + condOper + " "
										+ condValue + ")"); // print
							}
							if(ifElseStruct.isUimaTypeAndFeatSet()) {
								String typeAndFeat = ifElseStruct.getUimaTypeAndFeat();
								logger.debug("\t\t\t\t\tuimaTypeAndFeat: " + typeAndFeat); // print
							}
							else{
								String ontoRes = ifElseStruct.getOntoResUri();
								logger.debug("\t\t\t\t\tontoRes: " + ontoRes); // print
							}
						}
					} else {
						String typeAndFeat = placeholder.getTypeAndFeat();
						logger.debug("\t\t\ttypeAndFeat = " + typeAndFeat); // print
					}

				}

				// print the graph of the selected Projection Rule
				Collection<GraphStruct> graphList = pr.getGraphList();
				logger.debug("\tGRAPH:"); // print
				for (GraphStruct graph : graphList) {
					if (graph.isOptional())
						logger.debug("\t\tTRIPLE (OPTIONAL):"); // print
					else
						logger.debug("\t\tTRIPLE:"); // print
					GraphSingleElement subj = graph.getSubj();
					logger.debug("\t\t\tsubj:"); // print
					printGraphElement(subj);
					GraphSingleElement pred = graph.getPred();
					logger.debug("\t\t\tpred:"); // print
					printGraphElement(pred);
					GraphSingleElement obj = graph.getObj();
					logger.debug("\t\t\tobj:"); // print
					printGraphElement(obj);
					
				}

				// print the vars of the selected Projection Rule
				Collection<String> varsList = pr.getVarList();
				logger.debug("\tVARS:"); // print
				for (String var : varsList) {
					logger.debug("\t\tVAR: " + var); // print
				}

				// print the where part of the selected Projection Rule
				Collection<GraphStruct> whereList = pr.getWhereList();
				logger.debug("\tWHERE: "); // print
				for (GraphStruct graph : whereList) {
					logger.debug("\t\tTRIPLE:"); // print
					GraphSingleElement subj = graph.getSubj();
					logger.debug("\t\t\tsubj:"); // print
					printGraphElement(subj);
					GraphSingleElement pred = graph.getPred();
					logger.debug("\t\t\tpred:"); // print
					printGraphElement(pred);
					GraphSingleElement obj = graph.getObj();
					logger.debug("\t\t\tobj:"); // print
					printGraphElement(obj);
					if (graph.isOptional())
						logger.debug("\t\t\tOPTIONAL:"); // print
				}

				// print all the parameters of the selected Projection Rule
				Map<String, String> attMap = pr.getParametersMap();
				Set<String> keysList = attMap.keySet();
				logger.debug("\t\tAttributesList:");
				for (String attKey : keysList) {
					String attValue = attMap.get(attKey);
					logger.debug("\t\t\t" + attKey + "  =  " + attValue);
				}
			}
		}
	}

	private void printGraphElement(GraphSingleElement graphElem) {
		if (graphElem instanceof GraphSingleElemPlaceholder) {
			GraphSingleElemPlaceholder graphElemNode = (GraphSingleElemPlaceholder) graphElem;
			String nodeId = graphElemNode.getPlaceholderId();
			logger.debug("\t\t\t\tnodeId = " + nodeId); // print
			boolean extRef = graphElemNode.hasExternalReference();
			logger.debug("\t\t\t\textRef = " + extRef); // print
			PlaceholderStruct placeholder = graphElemNode.getPlaceholder();
			logger.debug("\t\t\t\tplaceholder = " + placeholder); // print
			if (extRef == true) {
				String extProjRule = graphElemNode.getOtherProjRuleId();
				logger.debug("\t\t\t\textProjRule = " + extProjRule); // print
			}
			boolean issue = graphElemNode.hasIssue();
			logger.debug("\t\t\t\tissue = " + issue); // print
		} else if (graphElem instanceof GraphSingleElemVar) {
			GraphSingleElemVar graphElemVar = (GraphSingleElemVar) graphElem;
			String varId = graphElemVar.getVarId();
			logger.debug("\t\t\t\tvarId = " + varId); // print
			boolean issue = graphElemVar.hasIssue();
			logger.debug("\t\t\t\tissue = " + issue); // print
		} else if (graphElem instanceof GraphSingleElemString) {
			GraphSingleElemString graphElemVar = (GraphSingleElemString) graphElem;
			String stringValue = graphElemVar.getStringValue();
			logger.debug("\t\t\t\tstringValue = " + stringValue); // print
			boolean issue = graphElemVar.hasIssue();
			logger.debug("\t\t\t\tissue = " + issue); // print
		} else {// graphElem instanceof GraphSingleElemURI
			GraphSingleElemUri graphElemUri = (GraphSingleElemUri) graphElem;
			String uri = graphElemUri.getURI();
			logger.debug("\t\t\t\turi = " + uri); // print
			boolean issue = graphElemUri.hasIssue();
			logger.debug("\t\t\t\tissue = " + issue); // print
		}
	}
}
